package javango.contrib.admin.views;

import static javango.contrib.freemarker.Helper.renderToResponse;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
//import org.hibernate.cfg.Configuration;
//import org.hibernate.tool.hbm2ddl.SchemaExport;

import com.google.inject.Inject;
import com.google.inject.Provider;

import javango.contrib.admin.ModelManager;
import javango.contrib.admin.FieldSet;
import javango.contrib.admin.MetaOptions;
import javango.contrib.admin.api.AdminOptions;
import javango.contrib.admin.util.Paginator;
import javango.contrib.databrowse.ObjectList;
import javango.contrib.hibernate.DaoException;
import javango.contrib.hibernate.ModelDao;
import javango.contrib.hibernate.HibernateUtil;
import javango.contrib.hibernate.ModelForm;
import javango.contrib.hibernate.Pagination;
import javango.contrib.jquery.Helper;
import javango.db.Manager;
import javango.db.ManagerException;
import javango.db.Managers;
import javango.db.QuerySet;
import javango.forms.fields.Field;
import javango.forms.fields.FieldFactory;
import javango.http.Http500;
import javango.http.HttpException;
import javango.http.HttpRequest;
import javango.http.HttpResponse;
import javango.http.HttpResponseRedirect;
import javango.http.SimpleHttpResponse;

public class Main {

	private final static Log log = LogFactory.getLog(Main.class);
	
	protected HibernateUtil hibernateUtil;
	protected FieldFactory fieldFactory;
	protected Provider<Helper> jqueryProvider;
	protected AdminOptions options;
	protected Managers managers;
	
	@Inject
	public Main(HibernateUtil hibernateUtil, FieldFactory fieldFactory, Provider<Helper> jqueryProvider, AdminOptions options, Managers managers) {
		super();
		this.hibernateUtil = hibernateUtil;
		this.fieldFactory = fieldFactory;
		this.jqueryProvider = jqueryProvider;
		this.options = options;
		this.managers = managers;
	}

	protected void updateContext(Map<String, Object> context , Class pc) {
		context.put("entity", new ModelDefinition(pc));
	}
	
	public class ModelDefinition {
		protected String className;
		protected String verboseName;
		
		public String getClassName() {
			return className;
		}

		public void setClassName(String className) {
			this.className = className;
		}

		public String getVerboseName() {
			return verboseName;
		}

		public void setVerboseName(String verboseName) {
			this.verboseName = verboseName;
		}

		public ModelDefinition(Class clazz) {
			super();
			this.className = clazz.getName();
			
			MetaOptions options = (MetaOptions)clazz.getAnnotation(MetaOptions.class);
			this.verboseName = options == null || "".equals(options.verboseName()) 
				? className.substring(className.lastIndexOf(".")+1) 
				: options.verboseName();
		}
		
		@Override
		public String toString() {
			return verboseName;
		}
		
	}
	
	public HttpResponse index(HttpRequest request) {
		Map<String, List<ModelDefinition>> packageMap = new HashMap<String, List<ModelDefinition>>();		
		
		Iterator<Class> i = options.getClassMappings();
		while (i.hasNext()) {
			Class pc = i.next();
			String className = pc.getName();
			String packageName =  className.substring(0,className.lastIndexOf("."));
			if (log.isDebugEnabled()) log.debug ("Adding class: " + pc.getName());
			if (packageMap.containsKey(packageName)) {
				List<ModelDefinition> appList = packageMap.get(packageName);
				appList.add(new ModelDefinition(pc));
			} else {
				List<ModelDefinition> appList = new ArrayList<ModelDefinition>();
				appList.add(new ModelDefinition(pc));
				packageMap.put(packageName, appList);
			}
		}
		log.debug("Complete parsing class mappings");
		
		Map<String, Object> context = new HashMap<String, Object>();
		context.put("package_map", packageMap);
		
		return renderToResponse("javango/contrib/admin/templates/index.ftl", context);
	}
	
	protected Long getPage(HttpRequest request) {
		String value = request.getParameter("p");
		try {
			return value == null ? 0 : Long.valueOf(value);
		} catch (NumberFormatException e) {
			log.debug(e,e);
			return new Long(0);
		}
	}
	
	protected String generateSearchParams(HttpRequest request, Map<String, String> map) {
		StringBuilder b = new StringBuilder();
		for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {			
			if (entry.getKey().contains("__")) {
				String[] value = entry.getValue();
				if (value != null && value.length > 0 & StringUtils.isNotEmpty(value[0])) {
					// TODO Add AND ability,  chain the String[] somehow...
					if (map != null) map.put(entry.getKey(), entry.getValue()[0]);
					b.append(String.format("%s=%s&", entry.getKey(), value[0]));
				}
			}
		}
		return b.toString();
	}
	
	public HttpResponse search(HttpRequest request, String model) throws HttpException {
		Class pc = options.getClassMapping(model);
		
		if (pc == null) {
			return new SimpleHttpResponse("Class not found");
		}
		
		ModelForm form = new ModelForm(fieldFactory, hibernateUtil, managers);
		form.setModel(pc);
		
		for (Field f : form.getFields().values()) {
			f.setRequired(false).setAllowNull(true);
			f.setName(f.getName() + "__eq");
		}
		
		if ("POST".equals(request.getMethod())) {
			form.bind(request.getParameterMap());
			if (form.isValid()) {
				// TODO better way to get the relative 'admin' part of the url
				Map<String, String> params = new HashMap<String, String>();
				generateSearchParams(request, params);
				return new HttpResponseRedirect(String.format("%s/admin/%s/", request.getContext(), model), params);
			}
		}
		
		Map<String, Object> context = new HashMap<String, Object>();
		context.put("form", form);
		updateContext(context, pc);
		return renderToResponse("/javango/contrib/admin/templates/search_form.ftl", context);

	}
	
	public HttpResponse changeList(HttpRequest request, String model) throws HttpException {
		try {		
			Class pc = options.getClassMapping(model);
			
			if (pc == null) {
				return new SimpleHttpResponse("Class not found");
			}
			
			Map<String, String> searchParams = new HashMap<String, String>();
			Manager<?> manager = managers.forClass(pc);
//			ModelDao<?> dao = new ModelDao(hibernateUtil, pc);
			QuerySet<?> qs = manager
				.filter(searchParams);
			Pagination pagination = new Pagination(new Long(10), getPage(request), qs.count());
			String query_string = generateSearchParams(request, searchParams);
			List<?> object_list = qs
				.limit(pagination.getFirst_on_page().intValue(), pagination.getLast_on_page().intValue())
				.list();

			ObjectList list = new ObjectList();
			list.generate(object_list, hibernateUtil.getConfiguration().getClassMapping(pc.getName()));
			
			Map<String, Object> context = new HashMap<String, Object>();
			context.put("list", list);
//			context.put("property_list", property_list);
//			context.put("object_list", object_list);
//			context.put("pk_list", pk_list);
			context.put("paginator", new Paginator(pagination, query_string));
			updateContext(context, pc);
					
			return renderToResponse("/javango/contrib/admin/templates/change_list.ftl", context);
		} catch (DaoException e) {
			throw new HttpException(e);
		} catch (ManagerException e) {
			throw new HttpException(e);
		}
	}
	
	public HttpResponse add(HttpRequest request, String model) throws HttpException {		
		return change(request, model, null);
	}
	
	protected String[] getIncludeFields(Class pc) {
		FieldSet fieldSet = (FieldSet)pc.getAnnotation(FieldSet.class);
		if (fieldSet != null) {
			return fieldSet.fields();
		}
		
		return null;

	}
	
	public HttpResponse change(HttpRequest request, String model, String object_id) throws HttpException {
		try {
			// TODO Changet object_id to an Object and figure out the correct datatype from the dao. dont' think this is necessary
			Class pc = options.getClassMapping(model);
			
			ModelManager bl = options.getManager(pc);
			ModelDao<?> dao = new ModelDao(hibernateUtil, pc);
			
			if (pc == null) {
				return new SimpleHttpResponse("Class not found");
			}
			
			List<String> property_list = options.getFieldList(pc);
			
			Object object = null;

			if (object_id != null) {
				object = bl.get(object_id);
				if (object == null) {
					return new SimpleHttpResponse("Object not found");
				}
			}
			
			String[] includeFields = getIncludeFields(pc);

			ModelForm form = new ModelForm(fieldFactory, hibernateUtil, managers);
			if (includeFields != null) form.setInclude(includeFields);
			form.setModel(pc);
			
			if ("POST".equals(request.getMethod())) {
				// update the object
				if (object == null) object=pc.newInstance();
				
				form.bind(request.getParameterMap());
				// form = modelFactory.form(pc.getMappedClass(), request.getParameterMap());
				if (form.isValid()) {
					if (object_id == null) { // this is a new object
						if (!bl.canCreate()) {
							throw new HttpException("Unable to create");
						}
					} else {
						if (!bl.canUpdate(object)) {
							throw new HttpException("Unable to updte");
						}
					}
					
					form.clean(object);					
					dao.save(object);
					
					if (request.getParameterMap().containsKey("_addanother")) {
						return new HttpResponseRedirect("../add");
					} else if (request.getParameterMap().containsKey("_continue")) {
						return new HttpResponseRedirect(String.format("../%d", bl.getPk(object)));
					}
					return new HttpResponseRedirect("..");
				}
			} else {
				// display the form
				if (object != null) form.setInitial(object);
			}
					
			Map<String, Object> context = new HashMap<String, Object>();
			context.put("property_list", property_list);
			context.put("object", object);
			context.put("form", form);
			context.put("object_id", object_id);
			context.put("jquery", jqueryProvider.get().setBasePath(request.getContext()).forForm(form));
			updateContext(context, pc);
					
			return renderToResponse("/javango/contrib/admin/templates/change_form.ftl", context);
		} catch (InstantiationException e) {
			log.error(e,e);
			throw new HttpException(e);
		} catch (IllegalAccessException e) {
			log.error(e,e);
			throw new HttpException(e);
		} catch (Exception e) {
			log.error(e,e);
			throw new HttpException(e);
		}
	}
	
	public HttpResponse delete(HttpRequest request, String model, String pk) throws HttpException {
		try {
			Class pc = options.getClassMapping(model);
			
			if (pc == null) {
				return new SimpleHttpResponse("Class not found");
			}
			
			ModelManager bl = options.getManager(pc);
			ModelDao<?> dao = new ModelDao(hibernateUtil, pc);

			Object object = bl.get(pk);
			if (object == null) {
				throw new Http500("Not found"); // TODO 404
			}
	
			if (!bl.canDelete(object)) {
				throw new HttpException("Unauthorized to delete");
			}
				
			if ("POST".equals(request.getMethod())) {
				dao.delete(object);
				return new HttpResponseRedirect("../..");
			}
			
			Map<String, Object> context = new HashMap<String, Object>();
			context.put("object", object);
			updateContext(context, pc);
			
			return renderToResponse("/javango/contrib/admin/templates/confirm_delete.ftl", context);
		} catch (Exception e) {
			log.error(e,e);
			throw new HttpException(e);
		}
	}
	
	public HttpResponse resetdb(HttpRequest request) {
		if ("POST".equals(request.getMethod())) {
			Configuration cfg = hibernateUtil.getConfiguration();
			new SchemaExport(cfg).drop(false, true);
			new SchemaExport(cfg).create(false, true);
			request.getSession().addMessage("Tables created successfully");
			return new HttpResponseRedirect("..");
		}
		
		return renderToResponse("/javango/contrib/admin/templates/confirm_reset_db.ftl", new HashMap<String, Object>());
	}
}
